//    Copyright (C) Kherty.  All rights reserved.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Diagnostics;
using System.Diagnostics.Contracts;

namespace OpenLS.Spreadsheet
{
    ///<summary>
    /// Collection of dimensions (rows or columns)
    ///</summary>
    ///<typeparam name="T">The type of dimension (row or column)</typeparam>
    public abstract class DimensionCollection<T> : INotifyCollectionChanged, IEnumerable<T>
        where T : Dimension

    {
        internal int Count
        {
            get
            {
#if DEBUG
                if (_data.Count > 1000000)
                    Debug.WriteLine("Warning, count > 1000000");
#endif
                return _data.Count;
            }
        }

        internal readonly StableIntSortedDictionary<T> _data = new StableIntSortedDictionary<T>();
        private IntervalManager _intervalManager;
        internal int MaxValidOffsetIndex = -1;
        private Worksheet _worksheet;

        protected DimensionCollection(Worksheet worksheet)
        {
            this._worksheet = worksheet;
        }

        private IntervalManager IntervalManager
        {
            get
            {
                if (_intervalManager != null && _intervalManager.ItemLength != DefaultLength)
                    _intervalManager = null;
                if (_intervalManager == null)
                {
                    _intervalManager = new IntervalManager(DefaultLength);
                    foreach (var pair in _data.Values   )
                    {
                        _intervalManager.Set(new IntervalManager.IntervalBounds(pair.Index, 1), pair.Length );
                    }
                }
                return _intervalManager;
            }
        }   

        protected abstract double DefaultLength { get; }

        ///<summary>
        /// Get the worksheet which contains the current collection.
        ///</summary>
        public Worksheet Worksheet
        {
            get { return _worksheet; }
            internal set { _worksheet = value; }
        }

        public T this[int index]
        {
            get
            {
                Contract.Assert(index >= 0);
                Contract.Assert(index <= MaxIndex);
                if (!ValidIndex(index))
                    throw new DimensionOutOfRangeException();
                if (index < 0)
                    throw new ArgumentException("index");
                T result;
                if (!_data.TryGetValue(index, out result))
                {
                    result = createItem(index);
                    Add(result);
                }
                return result;
            }
        }
        internal T TryGetItem (int index)
        {
            T result;
            _data.TryGetValue(index, out result);
            return result;
        }

        #region IEnumerable<T> Members

        public IEnumerator<T> GetEnumerator()
        {
            var keys = new List<int>(_data.Keys);
            keys.Sort();
            foreach (int i in keys)
                yield return _data[i];
            //\\return data.Values.GetEnumerator();
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }

        #endregion

        #region INotifyCollectionChanged Members

        /// <summary>
        /// Occurs when the collection changes.
        /// </summary>
        public event NotifyCollectionChangedEventHandler CollectionChanged;

        #endregion

        internal void ReForce(T c)
        {
            _data[c.Index] = c;
            c.IsDeleted = false;
        }

        internal double GetOffset(int index)
        {
            Contract.Requires(index >= 0);
            return IntervalManager.OffsetAt(index);

          //  Contract.Requires(ValidIndex(index));
          //  return this[index].Offset;
        }

        internal void TruncateOffsets(int index, double? newValue)
        {
            MaxValidOffsetIndex = Math.Min(MaxValidOffsetIndex, index - 1);
            if (newValue != null)
                IntervalManager.Set(new IntervalManager.IntervalBounds(index, 1), newValue.Value);
            else
            {
                _intervalManager = null;
            }
        }
        internal void ForgetIntervalManager()
        {
            _intervalManager = null;
        }

        [Obsolete]
        internal int GetIndexForOffset(double offset) //\\ inefficient
        {
            Contract.Ensures(Contract.Result<int>() >= 0);
            Contract.Ensures(Contract.Result<int>() == 0 || this[Contract.Result<int>()-1].Offset < offset);
            if (offset <= 0)
                return 0;
            int result = 0;
            Contract.Assert(result >= 0);
            int step = 1;
            while (GetOffset(result + 1) <= offset && result <= MaxIndex)
            {
                step *= 2;
                if (step > this.MaxIndex)
                    step = MaxIndex;
                result += step;
                Contract.Assert(result >= 0);
            }
            step = Math.Max(1, step/2);
            while (GetOffset(result + 1) > offset)
            {
                step = Math.Max(1, step/2);
                Contract.Assert(result >= 0);
                result -= step;
            }
            while (result > 0 && GetOffset(result - 1) == offset)
            {
                result--;
            }
            while (true)
            {
                if (result > MaxIndex)
                    return MaxIndex;
                if (result >= 0 && GetOffset(result) == offset)
                {
                    if (result == 0 || this[result - 1].Offset < offset)
                    {
                        Contract.Assert(result == 0 || this[result - 1].Offset < offset);
                        return result;
                    }
                }
                if (GetOffset(result + 1) > offset)
                {
                    Contract.Assert(result == 0 || this[result - 1].Offset < offset);
                    return result;
                }
                result++;
            }
        }
        internal abstract int MaxIndex
        {
            get;}


        internal void RaiseChanged(Dimension changed)

        {
            Worksheet.InvokeLayoutChanged(new DimensionEventArgs(changed));
        }
        [Obsolete("Use Worksheet.LayoutChanged instead")]
        internal event EventHandler LayoutChanged;

        internal void RaiseLayoutChanged(Dimension dimension)
        {
            if (LayoutChanged != null)
                LayoutChanged(this, new EventArgs());
            this.Worksheet.InvokeLayoutChanged(new DimensionEventArgs(dimension));
        }


        internal void RaiseCollectionChanged(NotifyCollectionChangedAction action, Dimension d, int index)
        {
            if (CollectionChanged != null)
                CollectionChanged(this, new NotifyCollectionChangedEventArgs(action, d, index));
            RaiseLayoutChanged(d);
        }

        internal void Remove(T d)
        {
            if (_worksheet.Workbook.UndoEnabled)
                _worksheet.Workbook.AddUndoUnit(new DeleteDimensionUndoUnit<T>(this, d));
            this.Workbook.IncrementRowColumnGeneration();
            var toMove = new List<Cell>();
            foreach (T t in _data.Values)
            {
                if (t.Index > d.Index)
                {
                    toMove.AddRange(t.GetNonEmptyCells());
                }
            }
            foreach (Cell cell in toMove)
                Worksheet.Cells.RemoveNonVolatileCellNoEvents(cell);
            _data.Remove(d.Index);
            OffsetItems(d.Index + 1, -1);
            foreach (Cell cell in toMove)
                Worksheet.Cells.AddNonVolatileCell(cell);
            TruncateOffsets(d.Index, null);
            RaiseCollectionChanged(NotifyCollectionChangedAction.Remove, d, d.Index);
        }

        internal void RemoveDontTouchCells(List<T> dims)
        {
            
            this.Workbook.IncrementRowColumnGeneration();
            if (_worksheet.Workbook.UndoEnabled)
                foreach (T t in Enumerable.Reverse (dims))
                {
                    _worksheet.Workbook.AddUndoUnit(new DeleteDimensionUndoUnit<T>(this, t));
                }
            foreach (T t in dims)
            {
                _data.Remove(t.Index);
            }
            T last = dims[dims.Count - 1];
            var l = new List<T>(Enumerable.Where(_data.Values, t => t.Index > last.Index));
            /* foreach (T t in data.Values)
            {
                if (t.Index > d.Index)
                {
                    l.Add(t);
                }
            }*/
            foreach (T t in l)
            {
                _data.Remove(t.Index);
                t.Index = t.Index - dims.Count;
            }
            foreach (T t in l)
                _data.Add(t.Index, t);
            TruncateOffsets(dims[0].Index, null);
            foreach (T t in dims)
            {
                t.IsDeleted = true;
            }
            NotifyChanges(dims);
        }

        internal void NotifyChanges(IEnumerable<T> l)
        {
            foreach (T t in l)
            {
                RaiseCollectionChanged(NotifyCollectionChangedAction.Remove, t, t.Index);
            }
        }

        internal void RemoveDontTouchCells(T d)
        {
            if (_worksheet.Workbook.UndoEnabled)
                _worksheet.Workbook.AddUndoUnit(new DeleteDimensionUndoUnit<T>(this, d));
            _data.Remove(d.Index);
            var l = new List<T>();
            foreach (T t in _data.Values)
            {
                if (t.Index > d.Index)
                {
                    l.Add(t);
                }
            }
            foreach (T t in l)
            {
                _data.Remove(t.Index);
                t.Index--;
            }
            foreach (T t in l)
                _data.Add(t.Index, t);
            TruncateOffsets(d.Index, null);
            RaiseCollectionChanged(NotifyCollectionChangedAction.Remove, d, d.Index);
        }

        internal void Add(T t)
        {
            _data.Add(t.Index, t);
            IntervalManager.Set(new IntervalManager.IntervalBounds(t.Index, 1), t.Length );
#if DEBUG
            if (_data.Count > 1000000)
                Debugger.Break();
#endif

        }

        internal  bool ValidIndex(int index)
        {
#if DEBUG
            if (_data.Count > 1000000)
                Debugger.Break();
#endif
            return index >= 0 && index <= MaxIndex;
        }

        private T createItem(int index)
        {
            Contract.Requires(index >= 0);
            Contract.Assert(!_data.ContainsKey(index));
            if (typeof (T) == typeof (Column))
                return (T) (object) new Column(Worksheet, index);
            var r = new Row(Worksheet) {Index = index};
            return (T) (object) r;
        }

        internal void Insert(int index)
        {
            insert(index, 1);
        }

        private void insert(int index, int incrementBy)
        {
            if (Worksheet.Workbook.UndoEnabled)
                Worksheet.Workbook.AddUndoUnit(new InsertDimensionUndoUnit<T>(this, index));
            this.Workbook.IncrementRowColumnGeneration();
            var l = new List<T>();
            var toMove = new List<Cell>();
            foreach (T t in _data.Values)
            {
                if (t.Index >= index)
                {
                    l.Add(t);
                    foreach (Cell c in t.GetNonEmptyCells())
                        toMove.Add(c);
                }
            }
            foreach (T t in l)
                _data.Remove(t.Index);
            foreach (Cell cell in toMove)
                Worksheet.Cells.RemoveCellNoEvents(cell);
            foreach (T t in l)
            {
                t.Index += incrementBy;
                _data.Add(t.Index, t);
            }
#if DEBUG
            if (_data.Count > 1000000)
                Debugger.Break();
#endif
            foreach (Cell cell in toMove)
                Worksheet.Cells.AddCell(cell);
            T item = createItem(index);
            Add(item);
            RaiseCollectionChanged(NotifyCollectionChangedAction.Add, this[index], index);
        }

        protected Workbook Workbook
        {
            get { return Worksheet.Workbook; }
        }

        internal void OffsetItems(int firstIndex, int incrementBy)
        {
            var toMove = new List<T>();
            foreach (T t in _data.Values)
            {
                if (t.Index >= firstIndex)
                    toMove.Add(t);
            }
            foreach (T t in toMove)
                _data.Remove(t.Index);
            foreach (T t in toMove)
            {
                t.Index += incrementBy;
            }
            foreach (T t in toMove)
                _data.Add(t.Index, t);
        }

        internal void InsertAt(T item, int index)
        {
            InsertAt(item, index, 1);
        }

        internal void InsertAt(T item, int index, int incrementBy)
        {
            if (Worksheet.Workbook.UndoEnabled)
                Worksheet.Workbook.AddUndoUnit(new InsertDimensionUndoUnit<T>(this, index));
            var l = new List<T>();
            var toMove = new List<Cell>();
            foreach (T t in _data.Values)
            {
                if (t.Index >= index)
                {
                    l.Add(t);
                    foreach (Cell c in t.GetNonEmptyCells())
                        toMove.Add(c);
                }
            }
            foreach (T t in l)
                _data.Remove(t.Index);
            foreach (Cell cell in toMove)
                Worksheet.NonVolatileCells.Remove(cell);
            foreach (T t in l)
            {
                t.Index += incrementBy;
                _data.Add(t.Index, t);
            }
            foreach (Cell cell in toMove)
                Worksheet.Cells.AddNonVolatileCell(cell);
//\\            T d = item;
            Add(item);
            RaiseCollectionChanged(NotifyCollectionChangedAction.Add, this[index], index);
        }

        internal void RemoveAt(int index)
        {
            Remove(this[index]);
        }

        internal void RaiseCollectionChanged(NotifyCollectionChangedAction notifyCollectionChangedAction)
        {
            if (CollectionChanged != null)
                CollectionChanged(this, new NotifyCollectionChangedEventArgs(notifyCollectionChangedAction));
        }

        internal void ExtendFormattingRecords(int firstIndex, int lastIndex)
        {
            if (firstIndex == 0)
                return;
            var previousDimension = this[firstIndex - 1];
            var previousRecord = previousDimension.FormattingRecord;
            Func<int, Cell, Cell> func;
            if (GetType() == typeof (RowCollection))
                func = (index, cell) => Worksheet[index, cell.Column.Index];
            else if (GetType() == typeof(ColumnCollection))
                func = (index, cell) => Worksheet[cell.Row.Index, index];
            else throw new NotSupportedException();
            for (int i = firstIndex; i <= lastIndex; i++)
            {
                this[i].SetFormattingRecord(previousRecord);
                foreach (var cell in previousDimension.GetCells())
                {
                    func(i, cell).SetFormattingRecordSafe(cell.FormattingRecord);
                }
            }
        }

        internal double GetLengthAt(int index)
        {
            return _intervalManager.LengthAt(index);

        }
    }
}